﻿using System;
using System.Collections.Generic;
using System.ComponentModel;

using JetBrains.Application.Progress;
using JetBrains.ProjectModel;
using JetBrains.ReSharper.Feature.Services.CodeCleanup;
using JetBrains.ReSharper.Feature.Services.CSharp.CodeCleanup;
using JetBrains.ReSharper.Psi;
using JetBrains.ReSharper.Psi.CSharp;
using JetBrains.ReSharper.Psi.CSharp.Tree;
using JetBrains.ReSharper.Psi.GeneratedCode;

namespace FinlogiK.ReSharperContrib
{
	/// <summary>
	/// 	Code cleanup module for arranging static qualifiers.
	/// </summary>
	[CodeCleanupModule(ModulesBefore = new[] { typeof(ArrangeThisQualifier), typeof(OptimizeUsings) })]
	public sealed class ArrangeStaticQualifierCleanupModule : ICodeCleanupModule
	{
		#region Fields

		private readonly ArrangeStaticQualifierCodeCleanupOptionDescriptor _descriptor = new ArrangeStaticQualifierCodeCleanupOptionDescriptor();

		#endregion

		#region Constructors

		/// <summary>
		/// 	Initializes a new instance of the <see cref = "ArrangeStaticQualifierCleanupModule" /> class.
		/// </summary>
		public ArrangeStaticQualifierCleanupModule()
		{
		}

		#endregion

		#region ICodeCleanupModule Members

		/// <summary>
		/// 	Get the collection of option descriptors
		/// </summary>
		/// <value></value>
		public ICollection<CodeCleanupOptionDescriptor> Descriptors
		{
			get
			{
				return new CodeCleanupOptionDescriptor[] { this._descriptor };
			}
		}

		/// <summary>
		/// 	Gets a value indicating whether the module is available on selection, or on the whole file.
		/// </summary>
		/// <value>
		/// 	The is available on selection.
		/// </value>
		public bool IsAvailableOnSelection
		{
			get
			{
				return false;
			}
		}

		/// <summary>
		/// 	Gets the language this module can operate.
		/// </summary>
		/// <value>
		/// 	The language type.
		/// </value>
		public PsiLanguageType LanguageType
		{
			get
			{
				return CSharpLanguageService.CSHARP;
			}
		}

		/// <summary>
		/// 	Check if this module can handle given project file
		/// </summary>
		/// <param name = "projectFile"></param>
		/// <returns></returns>
		public bool IsAvailable(IProjectFile projectFile)
		{
			PsiLanguageType languageType = ProjectFileLanguageServiceManager.Instance.GetPrimaryPsiLanguageType(projectFile);

			return (languageType == CSharpLanguageService.CSHARP);
		}

		/// <summary>
		/// 	Process clean-up on file. if <paramref name = "range" /> is <see cref = "F:JetBrains.DocumentModel.DocumentRange.InvalidRange" />, then process the whole file
		/// </summary>
		/// <param name = "projectFile"></param>
		/// <param name = "range"></param>
		/// <param name = "profile"></param>
		/// <param name = "canIncrementalUpdate"></param>
		/// <param name = "progressIndicator"></param>
		public void Process(IProjectFile projectFile, IPsiRangeMarker range, CodeCleanupProfile profile, out bool canIncrementalUpdate, IProgressIndicator progressIndicator)
		{
			canIncrementalUpdate = true;

			if (profile.GetSetting(this._descriptor))
			{
				if (!projectFile.IsGeneratedFile())
				{
					PsiManager manager = PsiManager.GetInstance(projectFile.GetSolution());
					ICSharpFile file = manager.GetPsiFile(projectFile, CSharpLanguageService.CSHARP) as ICSharpFile;

					if (file != null)
					{
						IEnumerable<IReferenceExpression> invalidReferences = ArrangeStaticQualifierCleanupModule.GetStaticReferencesWithoutQualifier(file);

						foreach (IReferenceExpression referenceExpression in invalidReferences)
						{
							ArrangeStaticQualifierHelper.AddStaticQualifier(referenceExpression);
						}
					}
				}
			}
		}

		/// <summary>
		/// 	Get default setting for given profile type
		/// </summary>
		/// <param name = "profile"></param>
		/// <param name = "profileType"></param>
		public void SetDefaultSetting(CodeCleanupProfile profile, CodeCleanup.DefaultProfileType profileType)
		{
			profile.SetSetting(this._descriptor, true);
		}

		#endregion

		#region Methods

		private static IEnumerable<IReferenceExpression> GetStaticReferencesWithoutQualifier(ICSharpFile file)
		{
			List<IReferenceExpression> invalidReferences = new List<IReferenceExpression>();
			RecursiveElementProcessor<IReferenceExpression> processor = new RecursiveElementProcessor<IReferenceExpression>(referenceExpression =>
			{
				if (ArrangeStaticQualifierHelper.IsReferenceToStaticFieldOrMethodWithoutQualifier(referenceExpression))
				{
					invalidReferences.Add(referenceExpression);
				}
			});

			file.ProcessDescendants(processor);

			return invalidReferences;
		}

		#endregion

		#region Nested Types

		[Category("FinlogiK Contrib")]
		[DefaultValue(true)]
		[DisplayName("Arrange static qualifier")]
		private sealed class ArrangeStaticQualifierCodeCleanupOptionDescriptor : CodeCleanupBoolOptionDescriptor
		{
			#region Constructors

			internal ArrangeStaticQualifierCodeCleanupOptionDescriptor()
				: base("FinlogiKArrangeStaticQualifier")
			{
			}

			#endregion
		}

		#endregion
	}
}